#include <fs.h>

size_t ramdisk_read(void *buf, size_t offset, size_t len);
size_t ramdisk_write(const void *buf, size_t offset, size_t len);

size_t serial_write(const void *buf, size_t offset, size_t len);
size_t events_read(void *buf, size_t offset, size_t len);
size_t dispinfo_read(void *buf, size_t offset, size_t len);
size_t fb_write(const void *buf, size_t offset, size_t len);
size_t sb_write(const void *buf, size_t offset, size_t len);
size_t sbctl_read(void *buf, size_t offset, size_t len);
size_t sbctl_write(const void *buf, size_t offset, size_t len);

typedef size_t (*ReadFn) (void *buf, size_t offset, size_t len);
typedef size_t (*WriteFn) (const void *buf, size_t offset, size_t len);

typedef struct {
    char *name;			// 文件名
    size_t size;		// 文件大小
    size_t disk_offset;	// 文件在 ramdisk 中的偏移

    ReadFn read;        // 读函数指针
    WriteFn write;      // 写函数指针
    size_t file_offset;	// 文件偏移量
} Finfo;

enum { FD_STDIN, FD_STDOUT, FD_STDERR, FD_EVENTS, FD_FB, FD_SB, FD_SBCTL };

size_t nr_finfo = 0;

size_t invalid_read(void *buf, size_t offset, size_t len) {
    panic("should not reach here");
    return 0;
}

size_t invalid_write(const void *buf, size_t offset, size_t len) {
    panic("should not reach here");
    return 0;
}

/* This is the information about all files in disk. */
static Finfo file_table[] __attribute__((used)) = {
    [FD_STDIN]  = {"stdin",          0, 0, invalid_read,  invalid_write },
    [FD_STDOUT] = {"stdout",         0, 0, invalid_read,  serial_write  },
    [FD_STDERR] = {"stderr",         0, 0, invalid_read,  serial_write  },
    [FD_EVENTS] = {"/dev/events",    0, 0, events_read,   invalid_write },
    [FD_FB]     = {"/dev/fb",        0, 0, invalid_read,  fb_write      },
    [FD_SB]     = {"/dev/sb",        0, 0, invalid_read,  sb_write      },
    [FD_SBCTL]  = {"/dev/sbctl",     0, 0, sbctl_read,    sbctl_write   },
                  {"/proc/dispinfo", 0, 0, dispinfo_read, invalid_write },
    // 包含文件记录表
    #include "files.h"
};

void init_fs() {
    // TODO: initialize the size of /dev/fb
    nr_finfo = sizeof(file_table) / sizeof(file_table[0]);
    AM_GPU_CONFIG_T config = io_read(AM_GPU_CONFIG);
    file_table[FD_FB].size = config.width * config.height * 4;
}

/* https://manpages.debian.org/unstable/manpages-zh/open.2.zh_CN.html */
int fs_open(const char *pathname, int flags, int mode) {
    for (int i = 0; i < nr_finfo; ++i) {
        if (strcmp(file_table[i].name, pathname) != 0) continue;
        // The file offset is set to the beginning of the file 文件的读写指针被置于文件头
        file_table[i].file_offset = 0;
        return i;
    }
    return -1;
}

/* https://manpages.debian.org/unstable/manpages-zh/read.2.zh_CN.html */
size_t fs_read(int fd, void *buf, size_t len) {
    assert(len >= 0);
    Finfo* f = &file_table[fd];
    size_t bytes = 0;

    if (f->read) {
        bytes = f->read(buf, f->file_offset, len);
        f->file_offset += bytes;
        return bytes;
    }

    size_t remain = f->size - f->file_offset;
    bytes = (len < remain) ? len : remain;
    ramdisk_read(buf, f->disk_offset + f->file_offset, bytes);
    f->file_offset += bytes;
    return bytes;
}

/* https://manpages.debian.org/unstable/manpages-zh/write.2.zh_CN.html */
size_t fs_write(int fd, const void *buf, size_t len) {
    assert(len >= 0);
    Finfo* f = &file_table[fd];
    size_t bytes = 0;

    if (f->write) {
        bytes = f->write(buf, f->file_offset, len);
        f->file_offset += bytes;
        return bytes;
    }

    size_t remain = f->size - f->file_offset;
    // assert(remain >= len);
    if (remain < len) return -1;
    bytes = len;
    ramdisk_write(buf, f->disk_offset + f->file_offset, bytes);
    f->file_offset += bytes;
    return bytes;
}

/* https://manpages.debian.org/unstable/manpages-dev/lseek.2.en.html */
size_t fs_lseek(int fd, int offset, int whence) {
    Finfo* f = &file_table[fd];

    // Log("fs_lseek: %s", f->name);
    switch (whence) {
        case SEEK_SET: /* The file offset is set to offset bytes. */
            f->file_offset = offset;
            break;
        case SEEK_CUR: /* The file offset is set to its current location plus offset bytes. */
            f->file_offset += offset;
            break;
        case SEEK_END: /* The file offset is set to the size of the file plus offset bytes. */
            f->file_offset = f->size +  offset;
            break;
        default:
            assert(0);
            break;
    }
    return f->file_offset;
}

/* https://manpages.debian.org/unstable/manpages-zh/close.2.zh_CN.html */
int fs_close(int fd) {
    return 0;
}
